package com.example.config;

import com.example.model.Device;
import com.example.model.DeviceType;
import com.example.service.Tenant;
import com.example.service.TenantAwareDataSourceFactory;
import com.example.store.DeviceCacheStore;
import com.example.store.DeviceCacheStoreFactory;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.cache.QueryIndex;
import org.apache.ignite.cache.QueryIndexType;
import org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory;
import org.apache.ignite.cache.store.jdbc.JdbcType;
import org.apache.ignite.cache.store.jdbc.JdbcTypeField;
import org.apache.ignite.cache.store.jdbc.dialect.MySQLDialect;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.spi.discovery.DiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder;

import javax.cache.configuration.FactoryBuilder;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

public class IgniteConfig {

    public static IgniteConfiguration getConfiguration() {
        IgniteConfiguration cfg = new IgniteConfiguration();

        // Basic configuration
        cfg.setIgniteInstanceName("MultiTenantGrid");
        cfg.setClientMode(false);
        TcpDiscoverySpi discoverySpi=new TcpDiscoverySpi();
        TcpDiscoveryMulticastIpFinder ipFinder= new TcpDiscoveryMulticastIpFinder();
        List<String> ips= new ArrayList<>();
        ips.add("127.0.0.1:47500..47509");
//        ips.add("127.0.0.1:47500");
//        ips.add("127.0.0.1:47501");
//        ips.add("127.0.0.1:47502");
//        ips.add("127.0.0.1:47503");
//        ips.add("127.0.0.1:47504");
//        ips.add("127.0.0.1:47505");
//        ips.add("127.0.0.1:47506");
//        ips.add("127.0.0.1:47507");
//        ips.add("127.0.0.1:47508");
//        ips.add("127.0.0.1:47509");
        ipFinder.setAddresses(ips);
        cfg.setDiscoverySpi(discoverySpi.setIpFinder(ipFinder));

        // Memory configuration
        DataStorageConfiguration storageCfg = new DataStorageConfiguration();
        DataRegionConfiguration defaultRegion = new DataRegionConfiguration();
        defaultRegion.setName("Default_Region");
        defaultRegion.setInitialSize(100 * 1024 * 1024); // 100MB
        defaultRegion.setMaxSize(1024 * 1024 * 1024);    // 1GB
        storageCfg.setDefaultDataRegionConfiguration(defaultRegion);
        cfg.setDataStorageConfiguration(storageCfg);

        // Cache configurations
        List<CacheConfiguration<?, ?>> caches = new ArrayList<>();

        // Configure caches for each tenant
        for (Tenant tenant : Tenant.values()) {
            caches.add(getDeviceTypeCacheConfiguration(tenant));
            caches.add(getDeviceCacheConfiguration(tenant));
        }

        cfg.setCacheConfiguration(caches.toArray(new CacheConfiguration[0]));
        return cfg;
    }

    private static CacheConfiguration<Long, DeviceType> getDeviceTypeCacheConfiguration(Tenant tenant) {
        CacheConfiguration<Long, DeviceType> ccfg = new CacheConfiguration<>();
        ccfg.setName("SQL_" + tenant.name() + "_DEVICE_TYPE");
        ccfg.setSqlSchema(tenant.getSchema());
        ccfg.setReadThrough(true);
        ccfg.setWriteThrough(true);

        // Configure store factory
        CacheJdbcPojoStoreFactory<Long, DeviceType> factory = new CacheJdbcPojoStoreFactory<>();
        factory.setDialect(new MySQLDialect());
        factory.setDataSourceFactory(new TenantAwareDataSourceFactory(tenant));

        JdbcType deviceTypeType = new JdbcType();
        deviceTypeType.setCacheName("SQL_" + tenant.name() + "_DEVICE_TYPE");
        deviceTypeType.setKeyType(Long.class);
        deviceTypeType.setValueType(DeviceType.class);
        deviceTypeType.setDatabaseSchema(tenant.getSchema());
        deviceTypeType.setDatabaseTable("DEVICE_TYPE");

        deviceTypeType.setKeyFields(new JdbcTypeField(Types.BIGINT, "id", Long.class, "id"));
        deviceTypeType.setValueFields(
                new JdbcTypeField(Types.BIGINT, "id", Long.class, "id"),
                new JdbcTypeField(Types.VARCHAR,"name",String.class,"name"),
                new JdbcTypeField(Types.VARCHAR,"description",String.class,"description"),
                new JdbcTypeField(Types.TINYINT,"active",Boolean.class,"active"));

        factory.setTypes(deviceTypeType);
//        ccfg.setCacheStoreFactory(new DeviceTypeCacheStoreFactory(tenant));
        ccfg.setCacheStoreFactory(factory);

        /* 将QueryEntity注册到缓存中以支持SQL查询,配置在XML中会更方便，*/
        /* QueryEntity对于基于SQL的查询很重要，尽量填完整它
        QueryEntity qryEntity = new QueryEntity();

        qryEntity.setKeyType(Long.class.getName());
        qryEntity.setValueType(DeviceType.class.getName());
        qryEntity.setKeyFieldName("id");
        Set<String> keyFields = new HashSet<>();
        keyFields.add("id");
        qryEntity.setKeyFields(keyFields);

        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
        fields.put("id", "java.lang.Long");
        fields.put("name", "java.lang.String");
        fields.put("description","java.lang.String");
        fields.put("active","java.lang.Boolean");
        qryEntity.setFields(fields);

        qryEntity.setIndexes(
                Arrays.asList(
                        new QueryIndex("id",QueryIndexType.SORTED,true,"device_type_id_unq"),
                        new QueryIndex("name",QueryIndexType.SORTED)
                )
        );

        ccfg.setQueryEntities(Collections.singletonList(qryEntity));
*/

        /* 方法2: QueryEntity(Key.class,Value.class)自动注册到缓存引擎中
         * 然后补齐keyFieldName
         * 此方法不会解析关联键，若没有关联键，本方法够用
        QueryEntity qryEntity = new QueryEntity(Long.class,DeviceType.class);
        qryEntity.setKeyFieldName("id");
        Set<String> keyFields = new HashSet<>();
        keyFields.add("id");
        qryEntity.setKeyFields(keyFields);
        */

        //方法3：此方法体终会调用new QueryEntity(Long.class,DeviceType.class)，生成的qryEntity缺少KeyFieldName等信息，需要补齐，
        //本方法调用会解析关联键，如果手动增加关联键，本调用可以省略
        ccfg.setIndexedTypes(Long.class, DeviceType.class);
        ccfg.getQueryEntities().forEach(
                qryEntity -> {
                    if (F.eq(qryEntity.findValueType(),DeviceType.class.getName())){
                        qryEntity.setKeyFieldName("id");
                        Set<String> keyFields = new HashSet<>();
                        keyFields.add("id");
                        qryEntity.setKeyFields(keyFields);
                    }
                }
        );

        /* 方法4: 仅调用ccfg.setIndexedTypes(Long.class, DeviceType.class);
         * 此时QueryEntity中没有KeyField信息, Insert Into 时，指定 _key 这个字段值也可以做到把数据加入缓存
         * 查询时
         */

        return ccfg;
    }

    private static CacheConfiguration<Long, Device> getDeviceCacheConfiguration(Tenant tenant) {
        CacheConfiguration<Long, Device> ccfg = new CacheConfiguration<>();
        ccfg.setName("SQL_" + tenant.name() + "_DEVICE");
        ccfg.setSqlSchema(tenant.getSchema());
        ccfg.setReadThrough(true);
        ccfg.setWriteThrough(true);

        // Configure store factory
        CacheJdbcPojoStoreFactory<Long, Device> factory = new CacheJdbcPojoStoreFactory<>();
        factory.setDialect(new MySQLDialect());
        factory.setDataSourceFactory(new TenantAwareDataSourceFactory(tenant));

        JdbcType deviceType = new JdbcType();
        deviceType.setCacheName("SQL_" + tenant.name() + "_DEVICE");
        deviceType.setKeyType(Long.class);
        deviceType.setValueType(Device.class);
        deviceType.setDatabaseSchema(tenant.getSchema());
        deviceType.setDatabaseTable("DEVICE");

        deviceType.setKeyFields(new JdbcTypeField(Types.BIGINT, "id", Long.class, "id"));
        deviceType.setValueFields(
                new JdbcTypeField(Types.BIGINT, "id", Long.class, "id"),
                new JdbcTypeField(Types.VARCHAR,"serial_number",String.class,"serialNumber"),
                new JdbcTypeField(Types.BIGINT,"device_type_id", Long.class,"deviceTypeId"),
                new JdbcTypeField(Types.VARCHAR,"name",String.class,"name"),
                new JdbcTypeField(Types.TINYINT,"active",Boolean.class,"active"),
                new JdbcTypeField(Types.DATE,"create_time", Date.class,"createTime"));

        factory.setTypes(deviceType);
        ccfg.setCacheStoreFactory(factory);

        QueryEntity qryEntity = new QueryEntity();

        qryEntity.setKeyType(Long.class.getName());
        qryEntity.setValueType(Device.class.getName());
        qryEntity.setKeyFieldName("id");
        Set<String> keyFields = new HashSet<>();
        keyFields.add("id");
        qryEntity.setKeyFields(keyFields);

        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
        fields.put("id", "java.lang.Long");
        fields.put("serialNumber","java.lang.String");
        fields.put("deviceTypeId","java.lang.Long");
        fields.put("name", "java.lang.String");
        fields.put("active","java.lang.Boolean");
        fields.put("createTime","java.util.Date");
        qryEntity.setFields(fields);

        qryEntity.setIndexes(
                Arrays.asList(
                        new QueryIndex("serialNumber",QueryIndexType.SORTED)
                )
        );

        ccfg.setQueryEntities(Collections.singletonList(qryEntity));


        // 自定义Cache Store 的情形
//        ccfg.setCacheStoreFactory(new DeviceCacheStoreFactory(tenant));


        // Configure queries
//        ccfg.setIndexedTypes(Long.class, Device.class);

        return ccfg;
    }
}